// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::sync::Arc;

pub(crate) mod iam;
pub(crate) mod service_account;

pub type Result<T> = std::result::Result<T, SigningError>;

/// An implementation of [crate::signer::SigningProvider] that wraps a dynamic provider.
///
/// This struct is the primary entry point for signing operations. It can be created
/// from any type that implements [SigningProvider].
#[derive(Clone, Debug)]
pub struct Signer {
    pub(crate) inner: Arc<dyn dynamic::SigningProvider>,
}

impl<T> std::convert::From<T> for Signer
where
    T: SigningProvider + Send + Sync + 'static,
{
    fn from(value: T) -> Self {
        Self {
            inner: Arc::new(value),
        }
    }
}

impl Signer {
    /// Returns the email address of the client performing the signing.
    ///
    /// This is typically the service account email.
    pub async fn client_email(&self) -> Result<String> {
        self.inner.client_email().await
    }

    /// Signs the provided content using the underlying provider.
    ///
    /// The content is typically a string-to-sign generated by the caller.
    pub async fn sign<T>(&self, content: T) -> Result<bytes::Bytes>
    where
        T: AsRef<[u8]> + Send + Sync,
    {
        self.inner.sign(content.as_ref()).await
    }
}

/// A trait for types that can sign content.
pub trait SigningProvider: std::fmt::Debug {
    /// Returns the email address of the authorizer.
    ///
    /// It is typically the Google service account client email address
    /// from the Google Developers Console in the form of
    /// "xxx@developer.gserviceaccount.com".
    fn client_email(&self) -> impl Future<Output = Result<String>> + Send;

    /// Signs the content.
    ///
    /// Returns the signature.
    fn sign(&self, content: &[u8]) -> impl Future<Output = Result<bytes::Bytes>> + Send;
}

pub(crate) mod dynamic {
    use super::Result;

    /// A dyn-compatible, crate-private version of `SigningProvider`.
    #[async_trait::async_trait]
    pub trait SigningProvider: Send + Sync + std::fmt::Debug {
        async fn client_email(&self) -> Result<String>;
        async fn sign(&self, content: &[u8]) -> Result<bytes::Bytes>;
    }

    /// The public CredentialsProvider implements the dyn-compatible CredentialsProvider.
    #[async_trait::async_trait]
    impl<T> SigningProvider for T
    where
        T: super::SigningProvider + Send + Sync,
    {
        async fn client_email(&self) -> Result<String> {
            T::client_email(self).await
        }

        async fn sign(&self, content: &[u8]) -> Result<bytes::Bytes> {
            T::sign(self, content).await
        }
    }
}

type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;

#[derive(thiserror::Error, Debug)]
#[error(transparent)]
pub struct SigningError(SigningErrorKind);

impl SigningError {
    /// A problem using API to sign blob.
    pub fn is_transport(&self) -> bool {
        matches!(self.0, SigningErrorKind::Transport(_))
    }

    /// A problem parsing a private key for local signing.
    pub fn is_parsing(&self) -> bool {
        matches!(self.0, SigningErrorKind::Parsing(_))
    }

    /// A problem signing content.
    pub fn is_sign(&self) -> bool {
        matches!(self.0, SigningErrorKind::Sign(_))
    }

    /// A problem parsing a private key for local signing.
    pub(crate) fn parsing<T>(source: T) -> SigningError
    where
        T: Into<BoxError>,
    {
        SigningError(SigningErrorKind::Parsing(source.into()))
    }

    /// A problem using API to sign blob.
    pub(crate) fn transport<T>(source: T) -> SigningError
    where
        T: Into<BoxError>,
    {
        SigningError(SigningErrorKind::Transport(source.into()))
    }

    /// A problem signing content.
    pub(crate) fn sign<T>(source: T) -> SigningError
    where
        T: Into<BoxError>,
    {
        SigningError(SigningErrorKind::Sign(source.into()))
    }

    /// Creates a new `SigningError`.
    ///
    /// This function is only intended for use in the client libraries
    /// implementation. Application may use this in mocks, though we do not
    /// recommend that you write tests for specific error cases.
    ///
    /// # Parameters
    /// * `message` - The underlying error that caused the signing failure.
    #[doc(hidden)]
    pub fn from_msg<T>(message: T) -> SigningError
    where
        T: Into<BoxError>,
    {
        SigningError(SigningErrorKind::Sign(message.into()))
    }
}

#[derive(thiserror::Error, Debug)]
enum SigningErrorKind {
    #[error("failed to generate signature via IAM API: {0}")]
    Transport(#[source] BoxError),
    #[error("failed to parse private key: {0}")]
    Parsing(#[source] BoxError),
    #[error("failed to sign content: {0}")]
    Sign(#[source] BoxError),
}

#[cfg(test)]
mod tests {
    use super::*;

    type TestResult = anyhow::Result<()>;

    mockall::mock! {
        #[derive(Debug)]
        Signer{}

        impl SigningProvider for Signer {
            async fn client_email(&self) -> Result<String>;
            async fn sign(&self, content: &[u8]) -> Result<bytes::Bytes>;
        }
    }

    #[tokio::test]
    async fn test_signer_success() -> TestResult {
        let mut mock = MockSigner::new();
        mock.expect_client_email()
            .returning(|| Ok("test".to_string()));
        mock.expect_sign()
            .returning(|_| Ok(bytes::Bytes::from("test")));
        let signer = Signer::from(mock);

        let result = signer.client_email().await?;
        assert_eq!(result, "test");
        let result = signer.sign("test").await?;
        assert_eq!(result, "test");

        Ok(())
    }

    #[tokio::test]
    async fn test_signer_error() -> TestResult {
        let mut mock = MockSigner::new();
        mock.expect_client_email()
            .returning(|| Err(SigningError::transport("test")));
        mock.expect_sign()
            .returning(|_| Err(SigningError::sign("test")));
        let signer = Signer::from(mock);

        let result = signer.client_email().await;
        assert!(result.is_err());
        assert!(result.unwrap_err().is_transport());
        let result = signer.sign("test").await;
        assert!(result.is_err());
        assert!(result.unwrap_err().is_sign());

        Ok(())
    }
}
